用Pytorch搭建卷积神经网络(以mnist时装数据集为例)

您所在的位置:网站首页 cnn batch 用Pytorch搭建卷积神经网络(以mnist时装数据集为例)

用Pytorch搭建卷积神经网络(以mnist时装数据集为例)

#用Pytorch搭建卷积神经网络(以mnist时装数据集为例)| 来源: 网络整理| 查看: 265

本文选取的数据集是mnist时装数据集,这也是计算机视觉领域的一个"hello world"级别的案例,非常适合新手入门。Kaggle比赛地址:https://www.kaggle.com/competitions/ece597697-sp2023/data.

一:基本操作

这里来介绍一下卷积神经网络的一些基本的操作,包括卷积、池化、激活函数、全连接层、softmax函数、损失函数。

卷积操作

卷积神经网络一般用于图像的处理。卷积操作一般用于提取图像的特征.

图像上每个像素点都是一个数据,我们可以把图像看出一个 n×m 的矩阵。

这里我们可以定义一个 x×y 的矩阵,这个矩阵就叫做卷积核.

卷积核一般选择 3×3 或者 5×5 的,小尺寸的卷积核可以捕获图像中的局部特征,同时避免过度拟合。与较大的卷积核相比,小尺寸的卷积核需要更少的参数,因此层数可以更深,模型可以更加复杂。

我们可以对图像做卷积操作.

我们直接举个例子来了解卷积操作是如何进行的:

我们设图像为 5×5 的矩阵: \begin{bmatrix}1&2&3&4&5\\6&7&8&9&10\\ 11&12&13&14&15\\ 16&17&18&19&20\\ 21&22&23&24&25\end{bmatrix}

我们使用的卷积核是 3×3 的矩阵: \begin{bmatrix}1&2&3\\4&5&6\\7&8&9\end{bmatrix} .

这里我们设步长为1,也就是卷积核每次移动一格,如果不理解也没关系,可以继续看下面的例子。

我们取出图片矩阵中左上角 3×3 的矩阵: \begin{bmatrix}1&2&3\\6&7&8\\ 11&12&13\end{bmatrix} .

将它们与卷积核相乘,这里不是矩阵的乘法,就是将两个矩阵对应位置的数相乘,然后再把数字相加起来.

即: 1×1+2×2+3×3+6×4+7×5+8×6+11×7+12×8+13×9=411

这样我们可以将运算后的结果放在矩阵中: \begin{bmatrix}411&X&X\\X&X&X\\ X&X&X\end{bmatrix}

步长为1,就是将卷积核向右移动一格,接下来和卷积核进行卷积操作的矩阵是: \begin{bmatrix}2&3&4\\7&8&9\\ 12&13&14\end{bmatrix} .

然后运算完成之后再填充入矩阵,放在411右边的位置。

对这一层的矩阵进行完卷积操作,由于步长为1,移动到下一层,接下来和卷积核进行卷积操作的矩阵是:

\begin{bmatrix}6&7&8\\ 11&12&13\\16&17&18\end{bmatrix}

如此循环,直到将图像遍历完成,就会输出一个比原图像小的矩阵: \begin{bmatrix}411&456&501\\636&681&726\\ 861&906& 951\end{bmatrix}

这里还可以将步长设置为2,对于图像 \begin{bmatrix}1&2&3&4&5\\6&7&8&9&10\\ 11&12&13&14&15\\ 16&17&18&19&20\\ 21&22&23&24&25\end{bmatrix} 来说,矩阵 \begin{bmatrix}1&2&3\\6&7&8\\ 11&12&13\end{bmatrix} 与卷积核进行卷积后,跳过一格,接下来是 \begin{bmatrix}3&4&5\\8&9&10\\ 13&14&15\end{bmatrix} 与卷积核进行操作。在换行的时候也是跳过一格,是 \begin{bmatrix} 11&12&13\\ 16&17&18\\ 21&22&23\end{bmatrix} 与卷积核进行操作,最后得到的是一个 2×2 的矩阵。

我们也可以在卷积运算的基础上加上一个偏置系数 b ,结果就是输出的矩阵每个数字都增加了 b 。

拿刚才步长为1的情况来说,就是 \begin{bmatrix}411+b&456+b&501+b\\636+b&681+b&726+b\\ 861+b&906+b& 951+b\end{bmatrix} 。

有时候为了让输出图像和原图像大小相等,还会在图像外围加上 padding 。

比如上面的 5×5 的矩阵,当 padding=1 时, \begin{bmatrix}0&0&0&0&0&0&0\\0&1&2&3&4&5&0\\0&6&7&8&9&10&0\\ 0&11&12&13&14&15&0\\ 0&16&17&18&19&20&0\\ 0&21&22&23&24&25&0\\0&0&0&0&0&0&0\end{bmatrix} .

这样对图像用 3×3 ,步长为1的卷积核进行卷积操作后,就会得到 5×5 的矩阵了。

我们知道,在线性回归 y=\mathbf{W}\mathbf{X}+b 中,已知的是 \mathbf{X}y ,我们要找的就是拟合数据的最优的 \mathbf{W} .

在卷积神经网络中,卷积核就是 \mathbf{W} ,它的初始值可以随便设,然后通过梯度下降法最小化损失函数来实现找到最优的卷积核参数。

至于卷积核该选择多大的,步长多少,要不要偏置系数,要不要 padding 。对于不同的数据我们也得进行不同的分析,只有不断地尝试,才能够找到最优的方案。

池化操作

池化可以对图片数据进行降维处理,常见的池化有最大池化,平均池化。

下面用 4×4 的矩阵来举例子: \begin{bmatrix}1&2&3&4\\5&6&7&8\\ 9&10&11&12\\ 13&14&15&16\end{bmatrix} .

我们比如选择 2×2 的池化窗口,步长为1.

最大池化就是对 \begin{bmatrix}1&2\\5&6\end{bmatrix} 取最大值,即6,然后移动到 \begin{bmatrix}2&3\\6&7\end{bmatrix} 中取最大值7,以此类推,步长的意思同卷积操作,最后得到池化后的矩阵: \begin{bmatrix}6&7&8\\10&11&12\\14&15&16\end{bmatrix} .

平均池化就是对 \begin{bmatrix}1&2\\5&6\end{bmatrix} 取平均值,例如 \frac{1+2+5+6}{4}=3.5 ,如果这时候不是整数,可以用向下取整的方式取3.

池化要考虑的同样有选择几乘几的池化窗口(池化核),选择平均池化还是最大池化,步长为多少,还有 padding 操作,这里也需要根据具体情况具体分析。

激活函数

在卷积操作中,我们将卷积核和图像在对应位置上进行相乘,再求和,这是一个线性的变换。我们还需要一些非线性的变换,来增加模型的表达能力。

下面介绍常见的几种激活函数:

ReLU 函数: f(x)=max(0,x) ,当 x 为负数的时候 f(x)=0 ,当 x 为正数的时候,它为 x .

例如图像: \begin{bmatrix}1&2\\-2&-1\end{bmatrix} 经过 ReLU 函数激活就会变成 \begin{bmatrix}1&2\\0&0\end{bmatrix} .

tanh(x) 函数: f(x)==\frac{e^x-e^{-x}}{e^x+e^{-x}} .

sigmoid 函数: f(x)=\frac{1}{1+e^{-x}}

LeakyReLU 函数: f(x)=max(0, x) + leak*min(0,x) .

当 x>0 时, f(x)=x ,当 x

上图这些红色的点按列来看是3层,分别有9个,5个,2个数据点。比如我们进行2分类,最后一层就放2个点。

这些连接每一层的黑色的线就是权重,它们的值和卷积核一样,也是随便设置的,之后会随着损失函数的降低会自动调整成最优的参数.

前向传播

这里拿下图来举个例子.

我们通过卷积和池化的操作得到了图像,然后将图像展平为一维向量 [x_1,x_2,……x_n] .

例如这里的 x_1,x_2 ,我们设置一个全连接层,设置输出的那层是2个点,即 y_1,y_2 .

我们已经初始化好了 w_1,w_2,w_3,w_4 .

故 y_1=x_1w_1+x_2w_3 , y_2=x_1w_2+x_2w_4 .

这样就将数据传入了下一层,由于我们是用梯度下降法更新权重,所以反向传播就是求偏导。

注:关于全连接层的可视化的代码:用 Python 实现全连接层网络可视化

softmax 函数

经过全连接层,我们进行几分类就会得到几个数据点。然后再对它们进行归一化处理,使这些数据点相加之和为1,并且每个点都是0~1之间的概率分布值。

softmax函数的表达式为 f(x)=\frac{e^i}{\sum\limits_{i=1}^n e^i} .

softmax 函数用到了指数函数 y=e^x ,所以当 x 变化一点, e^x 变化会很大。能够将差距大的数值差距变得更大,容易区分;但是,由于是指数函数,当 x 比较大的时候, e^x 会很大,容易出现数值溢出。 ^{[1]}

损失函数

softmax 作为输出节点的激活函数的时候,一般会将交叉熵作为损失函数.

因为 softmax 函数将输出的数据转化成了一个个0~1之间的概率值.

匀速小子:数学建模学习day27:交叉熵损失函数与KL散度

其他内容

本文搭建的只是一个最简单的卷积神经网络,其实还有 dropout 、正则化、图像增强等内容来提高预测的准确率,本文不涉及这些内容。

二:卷积神经网络思路讲解

本节主要讲解如何搭建卷积神经网络,并进行训练的代码部分,完整代码见第三节。

这里需要调用Pytorch相关的模块:

import torch#一个深度学习的库Pytorch import torch.nn as nn#neural network,神经网络 from torch.autograd import Variable#从自动求导中引入变量 import torch.optim as optim#一个实现了各种优化算法的库 import torch.nn.functional as F#神经网络函数库

在 class CNN 中,需要定义两个函数,一个是初始化函数 __init__函数,一个是前向传播forward函数.

首先,导入现成的 nn.Module ,这是现有的库,里面很多函数都准备好了,我们直接调用即可。

class CNN(nn.Module):

后面会看到,调用现有的库搭建卷积神经网络也需要100多行代码,想要在不调用现有库的前提下写代码,只能说很麻烦,也没有必要。

__init__函数

定义初始化函数,这个函数是用来搭建卷积神经网络的。

#初始化 def __init__(self):

把父类的方法继承过来:

#继承父类的所有方法 super(CNN,self).__init__()

构建序列化的神经网络,将网络层按照传入的顺序组合起来,这个在后面写前向传播forward的时候,只需要用for循环就可以从前往后遍历每一层神经网络了。

self.model=nn.Sequential()

在mnist数据集中都是 28×28 的图片,我这里写的是我目前实践出来的最好的卷积神经网络了。

第一层卷积神经网络:

在卷积神经网络中添加模块,命名为"conv1", nn.Conv2d 函数是用来处理二维图像数据的,传入1张图片,然后用16个 5×5 的卷积核来提取16个不同的特征,这个卷积核步长为1, padding=0 ,偏置项 bias=False 就是不加偏置项。

self.model.add_module('conv1',nn.Conv2d(1,16,5,1,0,bias=False))

经过这一次操作,输出的是16张 24×24 的图像。

然后加入一层最大池化层,命名为"maxpool1",池化核为 2×2 ,步长为2, padding=0 .

self.model.add_module("maxpool1",nn.MaxPool2d(2,2,0))

经过这一次操作,输出的是16张 12×12 的图像。

然后在这里加上激活函数 LeakyReLU 函数,命名为"lrelu1",这个激活函数比普通的 ReLU 函数效果好。

self.model.add_module('lrelu1',nn.LeakyReLU())

经过这一次操作,输出的是16张 12×12 的图像,图像里的值发生改变。

第二层卷积神经网络:

命名为"conv2", nn.Conv2d 函数是用来处理二维图像数据的,传入16张图片,然后用32个 5×5 的卷积核来提取32个不同的特征,这个卷积核步长为1, padding=0 ,偏置项 bias=False 就是不加偏置项。

self.model.add_module('conv2',nn.Conv2d(16,32,5,1,0,bias=False))

经过这一次操作,输出的是32张 8×8 的图像.

然后加入一层最大池化层,命名为"maxpool2",池化核为 2×2 ,步长为2, padding=0 .

self.model.add_module("maxpool2",nn.MaxPool2d(2,2,0))

经过这一次操作,输出的是32张 4×4 的图像。

这里用 sigmoid 函数来进行激活。

self.model.add_module('sigmoid1',nn.Sigmoid())

将图像展平成 32×4×4 的一维向量,这个操作放在forward函数里,最后用4层全连接层来输出10个数据点。

self.model.add_module('linear1',nn.Linear(32*16,320)) self.model.add_module('linear2',nn.Linear(320,120)) self.model.add_module('linear3',nn.Linear(120,84)) self.model.add_module('linear4',nn.Linear(84,10))

这样搭建没什么理由,看到有参考资料说120,84比较好用。

前向传播:forward函数

定义函数,传入一张 28×28 的图片:

def forward(self,input):

传入数据

output=input

因为前面已经将神经网络按传入顺序组合起来了,这里除了全连接层前面需要展平,其他只要输入输出即可。

#按照神经网络的顺序去跑一遍 for name,module in self.model.named_children(): #如果这一层是全连接层,那就先展平,得到一维的32*16的向量 if name=="linear1": output=output.view(-1,32*16) output=module(output)

self.model.named_children()是一个PyTorch模型对象(self.model)的方法,用于返回该模型所有子模块(即包含在该模型中的其他模型对象)及其名称的迭代器。具体来说,它返回一个迭代器,每次迭代会产生一个元组,第一个元素是子模块的名称,第二个元素是子模块本身。使用这个方法可以方便地访问和操作深度学习模型中的各个组件。 \color{blue}{注:关于output=output.view(-1,32*16).\\ 如果我们知道一个张量有32×16个数据,我们要把它变成32×16列的张量,但是如果不知道有几行,就可以写-1,让它自己计算。}

用 softmax 函数将输出的数据转化成0~1之间的概率,这里的 dim=1 是按行做归一化处理。

return F.softmax(output,dim=1)

这里返回的是一个张量.

参数的初始化

在卷积神经网络中卷积核中有很多参数,我们可以对卷积核按照正态分布的均值和标准差随机初始化。

def weight_init(m): #获取对象所属的类的名称 class_name=m.__class__.__name__ #当对象的name中出现"conv",也就是卷积操作 if class_name.find('conv')!=-1: #对卷积核按照正态分布的均值和标准差随机初始化 m.weight.data.normal_(0,0.02)

准备工作

对于数据的预处理部分留到第3部分,这里需要建立卷积神经网络、设置优化器。

初始化神经网络

netC=CNN() netC.apply(weight_init) print(netC)

设置优化器来优化梯度下降算法,这里选择的是OpenAI设计的Adam算法,学习率可以自适应。

optimizer=optim.Adam(netC.parameters(),lr=0.0002,betas=(0.5,0.999))

用交叉熵做损失函数,这里用负对数似然损失函数,这也是交叉熵损失函数的一种.

这个损失函数输出例如: -0.66 ,那就说明预测的准确率为 0.66 .

criterion=nn.NLLLoss()#负对数似然损失函数,也是交叉熵损失函数的一种

训练

这里每次选择200张图片,迭代2000次。

每次梯度清空,读入数据并预测,计算损失函数,反向传播,然后进行优化。

(下面的代码有注释,应该能看懂)

num=[i for i in range(len(train_X))] for epoch in range(num_epochs): #先将数组进行一次洗牌,然后一次选择batch_size张图片进行训练,训练完成计算完损失函数再进行梯度下降,然后将清零 np.random.shuffle(num) train_X=train_X[num] train_y=train_y[num] #将梯度清空 optimizer.zero_grad() image=[]#图片 label=[]#标签 #每次选择200张图片和标签读入 for i in range(batch_size): image.append(train_X[i].reshape((1,28,28))) label.append(train_y[i]) #将数据转换成可以处理的张量格式 image=torch.Tensor(image) label=torch.Tensor(label).long() #训练 netC.train() output=netC(image)#将数据放进去训练 #计算每次的损失函数 error=criterion(output,label) #反向传播 error.backward() #优化器进行优化(梯度下降,降低误差) optimizer.step() #迭代50次查看损失函数的效果 if epoch%50==0: print(epoch,error) errors.append(error)三:完整代码

导入相关的库

import pandas as pd#导入csv文件的库 import numpy as np#进行矩阵运算的库 import matplotlib.pyplot as plt#作图的库 import torch#一个深度学习的库Pytorch import torch.nn as nn#neural network,神经网络 from torch.autograd import Variable#从自动求导中引入变量 import torch.optim as optim#一个实现了各种优化算法的库 import torch.nn.functional as F#神经网络函数库 import warnings#避免一些可以忽略的报错 warnings.filterwarnings('ignore')

导入训练数据集

train=pd.read_csv("train.csv")

提取数据

train_y=train['label'].values train=train.drop(['label','id'],axis=1) train_X=train.values

划分训练集和测试集

num=[i for i in range(len(train_X))] np.random.shuffle(num) train_X=train_X[num] train_y=train_y[num] test_y=train_y[50000:] test_x=train_X[50000:] train_X=train_X[:50000] train_y=train_y[:50000]

卷积神经网络

#卷积神经网络made by yunsuxiaozi class CNN(nn.Module): #初始化 def __init__(self): #继承父类的所有方法 super(CNN,self).__init__() #构建序列化的神经网络,将网络层按照传入的顺序组合起来 self.model=nn.Sequential() #第一层卷积神经网络 #开始添加模块,命名为conv1,传入1通道图像,生成16个5*5的卷积核,步长为1,在图像周围填充0(padding)个像素点,偏置项不需要 self.model.add_module('conv1',nn.Conv2d(1,16,5,1,0,bias=False)) #28*28的图像传入后会生成24*24的图像 #最大池化第一层,2*2的池化核,然后步长为2,周围加上0层0 self.model.add_module("maxpool1",nn.MaxPool2d(2,2,0)) #此操作过后会变成12*12的图像 self.model.add_module('lrelu1',nn.LeakyReLU()) #第二层卷积神经网络 #将第一层那16张图片传入,传出16*2个特征,用16*2个5*5的卷积核进行特征提取 self.model.add_module('conv2',nn.Conv2d(16,32,5,1,0,bias=False)) #变成32个8*8的特征图 #最大池化第二层,2*2的池化核,然后步长为2,周围加上0层0。 self.model.add_module("maxpool2",nn.MaxPool2d(2,2,0)) #变成32个4*4的特征图 self.model.add_module('sigmoid1',nn.Sigmoid()) #全连接网络层,输入32*16,输出10个类别的概率 self.model.add_module('linear1',nn.Linear(32*16,320)) self.model.add_module('linear2',nn.Linear(320,120)) self.model.add_module('linear3',nn.Linear(120,84)) self.model.add_module('linear4',nn.Linear(84,10)) #前向传播 def forward(self,input): #传入数据 output=input #按照神经网络的顺序去跑一遍 for name,module in self.model.named_children(): #如果这一层是全连接层,那就先展平,得到一维的32*16的向量 if name=="linear1": output=output.view(-1,32*16) output=module(output) return F.softmax(output,dim=1)

卷积核权重初始化

def weight_init(m): #获取对象所属的类的名称 class_name=m.__class__.__name__ #当对象的name中出现"conv",也就是卷积操作 if class_name.find('conv')!=-1: #对卷积核按照正态分布的均值和标准差随机初始化 m.weight.data.normal_(0,0.02)

初始化神经网络,损失函数、优化器及相关参数

#初始化神经网络 netC=CNN() netC.apply(weight_init) print(netC) #优化器 optimizer=optim.Adam(netC.parameters(),lr=0.0002,betas=(0.5,0.999)) #损失函数 criterion=nn.NLLLoss()#负对数似然损失函数,也是交叉熵损失函数的一种 #训练周期为2000次 num_epochs=2000 errors=[] batch_size=200#一次输入200张图片,随机的图片进行梯度下降

训练数据

num=[i for i in range(len(train_X))] for epoch in range(num_epochs): #先将数组进行一次洗牌,然后一次选择batch_size张图片进行训练,训练完成计算完损失函数再进行梯度下降,然后将清零 np.random.shuffle(num) train_X=train_X[num] train_y=train_y[num] #将梯度清空 optimizer.zero_grad() image=[]#图片 label=[]#标签 #每次选择200张图片和标签读入 for i in range(batch_size): image.append(train_X[i].reshape((1,28,28))) label.append(train_y[i]) #将数据转换成可以处理的张量格式 image=torch.Tensor(image) label=torch.Tensor(label).long() #训练 netC.train() output=netC(image)#将数据放进去训练 #计算每次的损失函数 error=criterion(output,label) #反向传播 error.backward() #优化器进行优化(梯度下降,降低误差) optimizer.step() #迭代50次查看损失函数的效果 if epoch%50==0: print(epoch,error) errors.append(error)

作图看效果

这里x轴是迭代次数,y轴是预测的准确率,由于只是看个效果,所以代码简单了点.

x=[(50*i) for i in range(40)]#迭代次数 p=[]#预测准确率 for i in range(len(errors)): p.append(-float(errors[i].detach().cpu().numpy())) plt.plot(x,p) plt.show()

在训练集和测试集上预测的准确率:

pred_y=[] for i in range(len(train_X)): pred=netC(torch.Tensor(train_X[i].reshape((1,1,28,28))))[0] pred=np.argmax(pred.detach().cpu().numpy()) pred_y.append(pred) print("训练集的效果:",np.sum(pred_y==train_y)/len(train_y))

pred_y=[] for i in range(len(test_x)): pred=netC(torch.Tensor(test_x[i].reshape((1,1,28,28))))[0] pred=np.argmax(pred.detach().cpu().numpy()) pred_y.append(pred) print("测试集的效果:",np.sum(pred_y==test_y)/len(test_y))

看起来差不多,没有出现过拟合或者欠拟合的情况。

训练完成后导入真正的测试集,训练,提交即可。

导入数据并预测:

test=pd.read_csv("test.csv") test_X=test.drop(['id'],axis=1).values y_pred=[] for i in range(len(test_X)): pred=netC(torch.Tensor(test_X[i].reshape((1,1,28,28))))[0] pred=np.argmax(pred.detach().cpu().numpy()) y_pred.append(pred)

写入文件

#写入文件 import csv head=["id","label"] data=[] for i in range(len(y_pred)): data.append([(i),y_pred[i]]) with open("answer.csv",'w',newline='') as f: writer=csv.writer(f) writer.writerow(head) writer.writerows(data)四:总结

前面也提过,由于没有使用dropout,图像增强那些操作,所以最后预测的准确率只有83%,多次尝试发现,这个模型预测的准确率是在73%~84%的范围内波动,效果一般。在这个竞赛的其他选手基本都是接近90%的成绩。

同时,在用卷积神经网络处理图像问题时貌似只有不停地尝试找到最优解。

注:用同样的代码去尝试Kaggle上另外一个入门案例——手写数字识别Digit Recognizer | Kaggle预测准确率有97%。

如果想了解Conv2d函数的用法,可以看Conv2d - PyTorch 2.0 documentation。

还有卷积神经网络可视化的平台:

1.http://alexlenail.me/NN-SVG/

2.https://cbovar.github.io/ConvNetDraw/

3.https://poloclub.github.io/cnn-explainer/#article-convolution

参考资料:

1.触摸壹缕阳光:一文详解Softmax函数



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3